home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 4: GNU Archives / Linux Cubed Series 4 - GNU Archives.iso / gnu / cvs-1.8 / cvs-1 / cvs-1.8.1 / src / history.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-05-06  |  40.6 KB  |  1,485 lines

  1. /*
  2.  *
  3.  *    You may distribute under the terms of the GNU General Public License
  4.  *    as specified in the README file that comes with the CVS 1.0 kit.
  5.  *
  6.  * **************** History of Users and Module ****************
  7.  *
  8.  * LOGGING:  Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
  9.  *
  10.  * On For each Tag, Add, Checkout, Commit, Update or Release command,
  11.  * one line of text is written to a History log.
  12.  *
  13.  *    X date | user | CurDir | special | rev(s) | argument '\n'
  14.  *
  15.  * where: [The spaces in the example line above are not in the history file.]
  16.  *
  17.  *  X        is a single character showing the type of event:
  18.  *        T    "Tag" cmd.
  19.  *        O    "Checkout" cmd.
  20.  *        F    "Release" cmd.
  21.  *        W    "Update" cmd - No User file, Remove from Entries file.
  22.  *        U    "Update" cmd - File was checked out over User file.
  23.  *        G    "Update" cmd - File was merged successfully.
  24.  *        C    "Update" cmd - File was merged and shows overlaps.
  25.  *        M    "Commit" cmd - "Modified" file.
  26.  *        A    "Commit" cmd - "Added" file.
  27.  *        R    "Commit" cmd - "Removed" file.
  28.  *
  29.  *  date    is a fixed length 8-char hex representation of a Unix time_t.
  30.  *        [Starting here, variable fields are delimited by '|' chars.]
  31.  *
  32.  *  user    is the username of the person who typed the command.
  33.  *
  34.  *  CurDir    The directory where the action occurred.  This should be the
  35.  *        absolute path of the directory which is at the same level as
  36.  *        the "Repository" field (for W,U,G,C & M,A,R).
  37.  *
  38.  *  Repository    For record types [W,U,G,C,M,A,R] this field holds the
  39.  *        repository read from the administrative data where the
  40.  *        command was typed.
  41.  *        T    "A" --> New Tag, "D" --> Delete Tag
  42.  *            Otherwise it is the Tag or Date to modify.
  43.  *        O,F    A "" (null field)
  44.  *
  45.  *  rev(s)    Revision number or tag.
  46.  *        T    The Tag to apply.
  47.  *        O    The Tag or Date, if specified, else "" (null field).
  48.  *        F    "" (null field)
  49.  *        W    The Tag or Date, if specified, else "" (null field).
  50.  *        U    The Revision checked out over the User file.
  51.  *        G,C    The Revision(s) involved in merge.
  52.  *        M,A,R    RCS Revision affected.
  53.  *
  54.  *  argument    The module (for [TOUF]) or file (for [WUGCMAR]) affected.
  55.  *
  56.  *
  57.  *** Report categories: "User" and "Since" modifiers apply to all reports.
  58.  *            [For "sort" ordering see the "sort_order" routine.]
  59.  *
  60.  *   Extract list of record types
  61.  *
  62.  *    -e, -x [TOFWUGCMAR]
  63.  *
  64.  *        Extracted records are simply printed, No analysis is performed.
  65.  *        All "field" modifiers apply.  -e chooses all types.
  66.  *
  67.  *   Checked 'O'ut modules
  68.  *
  69.  *    -o, -w
  70.  *        Checked out modules.  'F' and 'O' records are examined and if
  71.  *        the last record for a repository/file is an 'O', a line is
  72.  *        printed.  "-w" forces the "working dir" to be used in the
  73.  *        comparison instead of the repository.
  74.  *
  75.  *   Committed (Modified) files
  76.  *
  77.  *    -c, -l, -w
  78.  *        All 'M'odified, 'A'dded and 'R'emoved records are examined.
  79.  *        "Field" modifiers apply.  -l forces a sort by file within user
  80.  *        and shows only the last modifier.  -w works as in Checkout.
  81.  *
  82.  *        Warning: Be careful with what you infer from the output of
  83.  *             "cvs hi -c -l".  It means the last time *you*
  84.  *             changed the file, not the list of files for which
  85.  *             you were the last changer!!!
  86.  *
  87.  *   Module history for named modules.
  88.  *    -m module, -l
  89.  *
  90.  *        This is special.  If one or more modules are specified, the
  91.  *        module names are remembered and the files making up the
  92.  *        modules are remembered.  Only records matching exactly those
  93.  *        files and repositories are shown.  Sorting by "module", then
  94.  *        filename, is implied.  If -l ("last modified") is specified,
  95.  *        then "update" records (types WUCG), tag and release records
  96.  *        are ignored and the last (by date) "modified" record.
  97.  *
  98.  *   TAG history
  99.  *
  100.  *    -T    All Tag records are displayed.
  101.  *
  102.  *** Modifiers.
  103.  *
  104.  *   Since ...        [All records contain a timestamp, so any report
  105.  *             category can be limited by date.]
  106.  *
  107.  *    -D date        - The "date" is parsed into a Unix "time_t" and
  108.  *              records with an earlier time stamp are ignored.
  109.  *    -r rev/tag    - A "rev" begins with a digit.  A "tag" does not.  If
  110.  *              you use this option, every file is searched for the
  111.  *              indicated rev/tag.
  112.  *    -t tag        - The "tag" is searched for in the history file and no
  113.  *              record is displayed before the tag is found.  An
  114.  *              error is printed if the tag is never found.
  115.  *    -b string    - Records are printed only back to the last reference
  116.  *              to the string in the "module", "file" or
  117.  *              "repository" fields.
  118.  *
  119.  *   Field Selections    [Simple comparisons on existing fields.  All field
  120.  *             selections are repeatable.]
  121.  *
  122.  *    -a        - All users.
  123.  *    -u user        - If no user is given and '-a' is not given, only
  124.  *              records for the user typing the command are shown.
  125.  *              ==> If -a or -u is not specified, just use "self".
  126.  *
  127.  *    -f filematch    - Only records in which the "file" field contains the
  128.  *              string "filematch" are considered.
  129.  *
  130.  *    -p repository    - Only records in which the "repository" string is a
  131.  *              prefix of the "repos" field are considered.
  132.  *
  133.  *    -m modulename    - Only records which contain "modulename" in the
  134.  *              "module" field are considered.
  135.  *
  136.  *
  137.  * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
  138.  *
  139.  *** Checked out files for username.  (default self, e.g. "dgg")
  140.  *    cvs hi            [equivalent to: "cvs hi -o -u dgg"]
  141.  *    cvs hi -u user        [equivalent to: "cvs hi -o -u user"]
  142.  *    cvs hi -o        [equivalent to: "cvs hi -o -u dgg"]
  143.  *
  144.  *** Committed (modified) files from the beginning of the file.
  145.  *    cvs hi -c [-u user]
  146.  *
  147.  *** Committed (modified) files since Midnight, January 1, 1990:
  148.  *    cvs hi -c -D 'Jan 1 1990' [-u user]
  149.  *
  150.  *** Committed (modified) files since tag "TAG" was stored in the history file:
  151.  *    cvs hi -c -t TAG [-u user]
  152.  *
  153.  *** Committed (modified) files since tag "TAG" was placed on the files:
  154.  *    cvs hi -c -r TAG [-u user]
  155.  *
  156.  *** Who last committed file/repository X?
  157.  *    cvs hi -c -l -[fp] X
  158.  *
  159.  *** Modified files since tag/date/file/repos?
  160.  *    cvs hi -c {-r TAG | -D Date | -b string}
  161.  *
  162.  *** Tag history
  163.  *    cvs hi -T
  164.  *
  165.  *** History of file/repository/module X.
  166.  *    cvs hi -[fpn] X
  167.  *
  168.  *** History of user "user".
  169.  *    cvs hi -e -u user
  170.  *
  171.  *** Dump (eXtract) specified record types
  172.  *    cvs hi -x [TOFWUGCMAR]
  173.  *
  174.  *
  175.  * FUTURE:        J[Join], I[Import]  (Not currently implemented.)
  176.  *
  177.  */
  178.  
  179. #include "cvs.h"
  180.  
  181. static struct hrec
  182. {
  183.     char *type;        /* Type of record (In history record) */
  184.     char *user;        /* Username (In history record) */
  185.     char *dir;        /* "Compressed" Working dir (In history record) */
  186.     char *repos;    /* (Tag is special.) Repository (In history record) */
  187.     char *rev;        /* Revision affected (In history record) */
  188.     char *file;        /* Filename (In history record) */
  189.     char *end;        /* Ptr into repository to copy at end of workdir */
  190.     char *mod;        /* The module within which the file is contained */
  191.     time_t date;    /* Calculated from date stored in record */
  192.     int idx;        /* Index of record, for "stable" sort. */
  193. } *hrec_head;
  194.  
  195.  
  196. static char *fill_hrec PROTO((char *line, struct hrec * hr));
  197. static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr));
  198. static int select_hrec PROTO((struct hrec * hr));
  199. static int sort_order PROTO((const PTR l, const PTR r));
  200. static int within PROTO((char *find, char *string));
  201. static time_t date_and_time PROTO((char *date_str));
  202. static void expand_modules PROTO((void));
  203. static void read_hrecs PROTO((char *fname));
  204. static void report_hrecs PROTO((void));
  205. static void save_file PROTO((char *dir, char *name, char *module));
  206. static void save_module PROTO((char *module));
  207. static void save_user PROTO((char *name));
  208.  
  209. #define ALL_REC_TYPES "TOFWUCGMAR"
  210. #define USER_INCREMENT    2
  211. #define FILE_INCREMENT    128
  212. #define MODULE_INCREMENT 5
  213. #define HREC_INCREMENT    128
  214.  
  215. static short report_count;
  216.  
  217. static short extract;
  218. static short v_checkout;
  219. static short modified;
  220. static short tag_report;
  221. static short module_report;
  222. static short working;
  223. static short last_entry;
  224. static short all_users;
  225.  
  226. static short user_sort;
  227. static short repos_sort;
  228. static short file_sort;
  229. static short module_sort;
  230.  
  231. #ifdef HAVE_RCS5
  232. static short tz_local;
  233. static time_t tz_seconds_east_of_GMT;
  234. static char *tz_name = "+0000";
  235. #else
  236. static char tz_name[] = "LT";
  237. #endif
  238.  
  239. static time_t since_date;
  240. static char since_rev[20];    /* Maxrev ~= 99.99.99.999 */
  241. static char since_tag[64];
  242. static struct hrec *last_since_tag;
  243. static char backto[128];
  244. static struct hrec *last_backto;
  245. static char rec_types[20];
  246.  
  247. static int hrec_count;
  248. static int hrec_max;
  249.  
  250. static char **user_list;    /* Ptr to array of ptrs to user names */
  251. static int user_max;        /* Number of elements allocated */
  252. static int user_count;        /* Number of elements used */
  253.  
  254. static struct file_list_str
  255. {
  256.     char *l_file;
  257.     char *l_module;
  258. } *file_list;            /* Ptr to array file name structs */
  259. static int file_max;        /* Number of elements allocated */
  260. static int file_count;        /* Number of elements used */
  261.  
  262. static char **mod_list;        /* Ptr to array of ptrs to module names */
  263. static int mod_max;        /* Number of elements allocated */
  264. static int mod_count;        /* Number of elements used */
  265.  
  266. static char *histfile;        /* Ptr to the history file name */
  267.  
  268. static const char *const history_usg[] =
  269. {
  270.     "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
  271.     "   Reports:\n",
  272.     "        -T              Produce report on all TAGs\n",
  273.     "        -c              Committed (Modified) files\n",
  274.     "        -o              Checked out modules\n",
  275.     "        -m <module>     Look for specified module (repeatable)\n",
  276.     "        -x [TOFWUCGMAR] Extract by record type\n",
  277.     "   Flags:\n",
  278.     "        -a              All users (Default is self)\n",
  279.     "        -e              Everything (same as -x, but all record types)\n",
  280.     "        -l              Last modified (committed or modified report)\n",
  281.     "        -w              Working directory must match\n",
  282.     "   Options:\n",
  283.     "        -D <date>       Since date (Many formats)\n",
  284.     "        -b <str>        Back to record with str in module/file/repos field\n",
  285.     "        -f <file>       Specified file (same as command line) (repeatable)\n",
  286.     "        -n <modulename> In module (repeatable)\n",
  287.     "        -p <repos>      In repository (repeatable)\n",
  288.     "        -r <rev/tag>    Since rev or tag (looks inside RCS files!)\n",
  289.     "        -t <tag>        Since tag record placed in history file (by anyone).\n",
  290.     "        -u <user>       For user name (repeatable)\n",
  291.     "        -z <tz>         Output for time zone <tz> (e.g. -z -0700)\n",
  292.     NULL};
  293.  
  294. /* Sort routine for qsort:
  295.    - If a user is selected at all, sort it first. User-within-file is useless.
  296.    - If a module was selected explicitly, sort next on module.
  297.    - Then sort by file.  "File" is "repository/file" unless "working" is set,
  298.      then it is "workdir/file".  (Revision order should always track date.)
  299.    - Always sort timestamp last.
  300. */
  301. static int
  302. sort_order (l, r)
  303.     const PTR l;
  304.     const PTR r;
  305. {
  306.     int i;
  307.     const struct hrec *left = (const struct hrec *) l;
  308.     const struct hrec *right = (const struct hrec *) r;
  309.  
  310.     if (user_sort)    /* If Sort by username, compare users */
  311.     {
  312.     if ((i = strcmp (left->user, right->user)) != 0)
  313.         return (i);
  314.     }
  315.     if (module_sort)    /* If sort by modules, compare module names */
  316.     {
  317.     if (left->mod && right->mod)
  318.         if ((i = strcmp (left->mod, right->mod)) != 0)
  319.         return (i);
  320.     }
  321.     if (repos_sort)    /* If sort by repository, compare them. */
  322.     {
  323.     if ((i = strcmp (left->repos, right->repos)) != 0)
  324.         return (i);
  325.     }
  326.     if (file_sort)    /* If sort by filename, compare files, NOT dirs. */
  327.     {
  328.     if ((i = strcmp (left->file, right->file)) != 0)
  329.         return (i);
  330.  
  331.     if (working)
  332.     {
  333.         if ((i = strcmp (left->dir, right->dir)) != 0)
  334.         return (i);
  335.  
  336.         if ((i = strcmp (left->end, right->end)) != 0)
  337.         return (i);
  338.     }
  339.     }
  340.  
  341.     /*
  342.      * By default, sort by date, time
  343.      * XXX: This fails after 2030 when date slides into sign bit
  344.      */
  345.     if ((i = ((long) (left->date) - (long) (right->date))) != 0)
  346.     return (i);
  347.  
  348.     /* For matching dates, keep the sort stable by using record index */
  349.     return (left->idx - right->idx);
  350. }
  351.  
  352. static time_t
  353. date_and_time (date_str)
  354.     char *date_str;
  355. {
  356.     time_t t;
  357.  
  358.     t = get_date (date_str, (struct timeb *) NULL);
  359.     if (t == (time_t) - 1)
  360.     error (1, 0, "Can't parse date/time: %s", date_str);
  361.     return (t);
  362. }
  363.  
  364. int
  365. history (argc, argv)
  366.     int argc;
  367.     char **argv;
  368. {
  369.     int i, c;
  370.     char fname[PATH_MAX];
  371.  
  372.     if (argc == -1)
  373.     usage (history_usg);
  374.  
  375.     optind = 1;
  376.     while ((c = getopt (argc, argv, "Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
  377.     {
  378.     switch (c)
  379.     {
  380.         case 'T':            /* Tag list */
  381.         report_count++;
  382.         tag_report++;
  383.         break;
  384.         case 'a':            /* For all usernames */
  385.         all_users++;
  386.         break;
  387.         case 'c':
  388.         report_count++;
  389.         modified = 1;
  390.         break;
  391.         case 'e':
  392.         report_count++;
  393.         extract++;
  394.         (void) strcpy (rec_types, ALL_REC_TYPES);
  395.         break;
  396.         case 'l':            /* Find Last file record */
  397.         last_entry = 1;
  398.         break;
  399.         case 'o':
  400.         report_count++;
  401.         v_checkout = 1;
  402.         break;
  403.         case 'w':            /* Match Working Dir (CurDir) fields */
  404.         working = 1;
  405.         break;
  406.         case 'X':            /* Undocumented debugging flag */
  407.         histfile = optarg;
  408.         break;
  409.         case 'D':            /* Since specified date */
  410.         if (*since_rev || *since_tag || *backto)
  411.         {
  412.             error (0, 0, "date overriding rev/tag/backto");
  413.             *since_rev = *since_tag = *backto = '\0';
  414.         }
  415.         since_date = date_and_time (optarg);
  416.         break;
  417.         case 'b':            /* Since specified file/Repos */
  418.         if (since_date || *since_rev || *since_tag)
  419.         {
  420.             error (0, 0, "backto overriding date/rev/tag");
  421.             *since_rev = *since_tag = '\0';
  422.             since_date = 0;
  423.         }
  424.         if (strlen (optarg) >= sizeof (backto))
  425.         {
  426.             error (0, 0, "backto truncated to %d bytes",
  427.                sizeof (backto) - 1);
  428.             optarg[sizeof (backto) - 1] = '\0';
  429.         }
  430.         (void) strcpy (backto, optarg);
  431.         break;
  432.         case 'f':            /* For specified file */
  433.         save_file ("", optarg, (char *) NULL);
  434.         break;
  435.         case 'm':            /* Full module report */
  436.         report_count++;
  437.         module_report++;
  438.         case 'n':            /* Look for specified module */
  439.         save_module (optarg);
  440.         break;
  441.         case 'p':            /* For specified directory */
  442.         save_file (optarg, "", (char *) NULL);
  443.         break;
  444.         case 'r':            /* Since specified Tag/Rev */
  445.         if (since_date || *since_tag || *backto)
  446.         {
  447.             error (0, 0, "rev overriding date/tag/backto");
  448.             *since_tag = *backto = '\0';
  449.             since_date = 0;
  450.         }
  451.         (void) strcpy (since_rev, optarg);
  452.         break;
  453.         case 't':            /* Since specified Tag/Rev */
  454.         if (since_date || *since_rev || *backto)
  455.         {
  456.             error (0, 0, "tag overriding date/marker/file/repos");
  457.             *since_rev = *backto = '\0';
  458.             since_date = 0;
  459.         }
  460.         (void) strcpy (since_tag, optarg);    /* tag */
  461.         break;
  462.         case 'u':            /* For specified username */
  463.         save_user (optarg);
  464.         break;
  465.         case 'x':
  466.         report_count++;
  467.         extract++;
  468.         {
  469.             char *cp;
  470.  
  471.             for (cp = optarg; *cp; cp++)
  472.             if (!strchr (ALL_REC_TYPES, *cp))
  473.                 error (1, 0, "%c is not a valid report type", *cp);
  474.         }
  475.         (void) strcpy (rec_types, optarg);
  476.         break;
  477.         case 'z':
  478. #ifndef HAVE_RCS5
  479.         error (0, 0, "-z not supported with RCS 4");
  480. #else
  481.         tz_local = 
  482.             (optarg[0] == 'l' || optarg[0] == 'L')
  483.             && (optarg[1] == 't' || optarg[1] == 'T')
  484.             && !optarg[2];
  485.         if (tz_local)
  486.             tz_name = optarg;
  487.         else
  488.         {
  489.             /*
  490.              * Convert a known time with the given timezone to time_t.
  491.              * Use the epoch + 23 hours, so timezones east of GMT work.
  492.              */
  493.             static char f[] = "1/1/1970 23:00 %s";
  494.             char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg));
  495.             time_t t;
  496.             sprintf (buf, f, optarg);
  497.             t = get_date (buf, (struct timeb *) NULL);
  498.             free (buf);
  499.             if (t == (time_t) -1)
  500.             error (0, 0, "%s is not a known time zone", optarg);
  501.             else
  502.             {
  503.             /*
  504.              * Convert to seconds east of GMT, removing the
  505.              * 23-hour offset mentioned above.
  506.              */
  507.             tz_seconds_east_of_GMT = (time_t)23 * 60 * 60  -  t;
  508.             tz_name = optarg;
  509.             }
  510.         }
  511. #endif
  512.         break;
  513.         case '?':
  514.         default:
  515.         usage (history_usg);
  516.         break;
  517.     }
  518.     }
  519.     c = optind;                /* Save the handled option count */
  520.  
  521.     /* ================ Now analyze the arguments a bit */
  522.     if (!report_count)
  523.     v_checkout++;
  524.     else if (report_count > 1)
  525.     error (1, 0, "Only one report type allowed from: \"-Tcomx\".");
  526.  
  527. #ifdef CLIENT_SUPPORT
  528.     if (client_active)
  529.     {
  530.     struct file_list_str *f1;
  531.     char **mod;
  532.  
  533.     /* We're the client side.  Fire up the remote server.  */
  534.     start_server ();
  535.     
  536.     ign_setup ();
  537.  
  538.     if (tag_report)
  539.         send_arg("-T");
  540.     if (all_users)
  541.         send_arg("-a");
  542.     if (modified)
  543.         send_arg("-c");
  544.     if (last_entry)
  545.         send_arg("-l");
  546.     if (v_checkout)
  547.         send_arg("-o");
  548.     if (working)
  549.         send_arg("-w");
  550.     if (histfile)
  551.         send_arg("-X");
  552.     if (since_date)
  553.         option_with_arg ("-D", asctime (gmtime (&since_date)));
  554.     if (backto[0] != '\0')
  555.         option_with_arg ("-b", backto);
  556.     for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
  557.     {
  558.         if (f1->l_file[0] == '*')
  559.         option_with_arg ("-p", f1->l_file + 1);
  560.         else
  561.         option_with_arg ("-f", f1->l_file);
  562.     }
  563.     if (module_report)
  564.         send_arg("-m");
  565.     for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
  566.         option_with_arg ("-n", *mod);
  567.     if (since_rev != NULL)
  568.         option_with_arg ("-r", since_rev);
  569.     if (since_tag != NULL)
  570.         option_with_arg ("-t", since_tag);
  571.     for (mod = user_list; mod < &user_list[user_count]; ++mod)
  572.         option_with_arg ("-u", *mod);
  573.     if (extract)
  574.         option_with_arg ("-x", rec_types);
  575.     option_with_arg ("-z", tz_name);
  576.  
  577.     send_to_server ("history\012", 0);
  578.         return get_responses_and_close ();
  579.     }
  580. #endif
  581.  
  582.     if (all_users)
  583.     save_user ("");
  584.  
  585.     if (mod_list)
  586.     expand_modules ();
  587.  
  588.     if (tag_report)
  589.     {
  590.     if (!strchr (rec_types, 'T'))
  591.         (void) strcat (rec_types, "T");
  592.     }
  593.     else if (extract)
  594.     {
  595.     if (user_list)
  596.         user_sort++;
  597.     }
  598.     else if (modified)
  599.     {
  600.     (void) strcpy (rec_types, "MAR");
  601.     /*
  602.      * If the user has not specified a date oriented flag ("Since"), sort
  603.      * by Repository/file before date.  Default is "just" date.
  604.      */
  605.     if (!since_date && !*since_rev && !*since_tag && !*backto)
  606.     {
  607.         repos_sort++;
  608.         file_sort++;
  609.         /*
  610.          * If we are not looking for last_modified and the user specified
  611.          * one or more users to look at, sort by user before filename.
  612.          */
  613.         if (!last_entry && user_list)
  614.         user_sort++;
  615.     }
  616.     }
  617.     else if (module_report)
  618.     {
  619.     (void) strcpy (rec_types, last_entry ? "OMAR" : ALL_REC_TYPES);
  620.     module_sort++;
  621.     repos_sort++;
  622.     file_sort++;
  623.     working = 0;            /* User's workdir doesn't count here */
  624.     }
  625.     else
  626.     /* Must be "checkout" or default */
  627.     {
  628.     (void) strcpy (rec_types, "OF");
  629.     /* See comments in "modified" above */
  630.     if (!last_entry && user_list)
  631.         user_sort++;
  632.     if (!since_date && !*since_rev && !*since_tag && !*backto)
  633.         file_sort++;
  634.     }
  635.  
  636.     /* If no users were specified, use self (-a saves a universal ("") user) */
  637.     if (!user_list)
  638.     save_user (getcaller ());
  639.  
  640.     /* If we're looking back to a Tag value, must consider "Tag" records */
  641.     if (*since_tag && !strchr (rec_types, 'T'))
  642.     (void) strcat (rec_types, "T");
  643.  
  644.     argc -= c;
  645.     argv += c;
  646.     for (i = 0; i < argc; i++)
  647.     save_file ("", argv[i], (char *) NULL);
  648.  
  649.     if (histfile)
  650.     (void) strcpy (fname, histfile);
  651.     else
  652.     (void) sprintf (fname, "%s/%s/%s", CVSroot,
  653.             CVSROOTADM, CVSROOTADM_HISTORY);
  654.  
  655.     read_hrecs (fname);
  656.     qsort ((PTR) hrec_head, hrec_count, sizeof (struct hrec), sort_order);
  657.     report_hrecs ();
  658.  
  659.     return (0);
  660. }
  661.  
  662. void
  663. history_write (type, update_dir, revs, name, repository)
  664.     int type;
  665.     char *update_dir;
  666.     char *revs;
  667.     char *name;
  668.     char *repository;
  669. {
  670.     char fname[PATH_MAX], workdir[PATH_MAX], homedir[PATH_MAX];
  671.     char *username = getcaller ();
  672.     int fd;
  673.     char *line;
  674.     char *slash = "", *cp, *cp2, *repos;
  675.     int i;
  676.     static char *tilde = "";
  677.     static char *PrCurDir = NULL;
  678.  
  679.     if (logoff)            /* History is turned off by cmd line switch */
  680.     return;
  681.     (void) sprintf (fname, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_HISTORY);
  682.  
  683.     /* turn off history logging if the history file does not exist */
  684.     if (!isfile (fname))
  685.     {
  686.     logoff = 1;
  687.     return;
  688.     }
  689.  
  690.     if (trace)
  691. #ifdef SERVER_SUPPORT
  692.     fprintf (stderr, "%c-> fopen(%s,a)\n",
  693.          (server_active) ? 'S' : ' ', fname);
  694. #else
  695.     fprintf (stderr, "-> fopen(%s,a)\n", fname);
  696. #endif
  697.     if (noexec)
  698.     return;
  699.     fd = open (fname, O_WRONLY | O_APPEND | O_CREAT | OPEN_BINARY, 0666);
  700.     if (fd < 0)
  701.     error (1, errno, "cannot open history file: %s", fname);
  702.  
  703.     repos = Short_Repository (repository);
  704.  
  705.     if (!PrCurDir)
  706.     {
  707.     char *pwdir;
  708.  
  709.     pwdir = get_homedir ();
  710.     PrCurDir = CurDir;
  711.     if (pwdir != NULL)
  712.     {
  713.         /* Assumes neither CurDir nor pwdir ends in '/' */
  714.         i = strlen (pwdir);
  715.         if (!strncmp (CurDir, pwdir, i))
  716.         {
  717.         PrCurDir += i;        /* Point to '/' separator */
  718.         tilde = "~";
  719.         }
  720.         else
  721.         {
  722.         /* Try harder to find a "homedir" */
  723.         if (!getwd (workdir))
  724.             error (1, errno, "can't getwd in history");
  725.         if (chdir (pwdir) < 0)
  726.             error (1, errno, "can't chdir(%s)", pwdir);
  727.         if (!getwd (homedir))
  728.             error (1, errno, "can't getwd in %s", pwdir);
  729.         (void) chdir (workdir);
  730.  
  731.         i = strlen (homedir);
  732.         if (!strncmp (CurDir, homedir, i))
  733.         {
  734.             PrCurDir += i;    /* Point to '/' separator */
  735.             tilde = "~";
  736.         }
  737.         }
  738.     }
  739.     }
  740.  
  741.     if (type == 'T')
  742.     {
  743.     repos = update_dir;
  744.     update_dir = "";
  745.     }
  746.     else if (update_dir && *update_dir)
  747.     slash = "/";
  748.     else
  749.     update_dir = "";
  750.  
  751.     (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
  752.  
  753.     /*
  754.      * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
  755.      * "repos"    is the Repository, relative to $CVSROOT where the RCS file is.
  756.      *
  757.      * "$workdir/$name" is the working file name.
  758.      * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
  759.      *
  760.      * First, note that the history format was intended to save space, not
  761.      * to be human readable.
  762.      *
  763.      * The working file directory ("workdir") and the Repository ("repos")
  764.      * usually end with the same one or more directory elements.  To avoid
  765.      * duplication (and save space), the "workdir" field ends with
  766.      * an integer offset into the "repos" field.  This offset indicates the
  767.      * beginning of the "tail" of "repos", after which all characters are
  768.      * duplicates.
  769.      *
  770.      * In other words, if the "workdir" field has a '*' (a very stupid thing
  771.      * to put in a filename) in it, then every thing following the last '*'
  772.      * is a hex offset into "repos" of the first character from "repos" to
  773.      * append to "workdir" to finish the pathname.
  774.      *
  775.      * It might be easier to look at an example:
  776.      *
  777.      *  M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
  778.      *
  779.      * Indicates that the workdir is really "~/work/cvs/examples", saving
  780.      * 10 characters, where "~/work*d" would save 6 characters and mean that
  781.      * the workdir is really "~/work/examples".  It will mean more on
  782.      * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
  783.      *
  784.      * "workdir" is always an absolute pathname (~/xxx is an absolute path)
  785.      * "repos" is always a relative pathname.  So we can assume that we will
  786.      * never run into the top of "workdir" -- there will always be a '/' or
  787.      * a '~' at the head of "workdir" that is not matched by anything in
  788.      * "repos".  On the other hand, we *can* run off the top of "repos".
  789.      *
  790.      * Only "compress" if we save characters.
  791.      */
  792.  
  793.     if (!repos)
  794.     repos = "";
  795.  
  796.     cp = workdir + strlen (workdir) - 1;
  797.     cp2 = repos + strlen (repos) - 1;
  798.     for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
  799.     i++;
  800.  
  801.     if (i > 2)
  802.     {
  803.     i = strlen (repos) - i;
  804.     (void) sprintf ((cp + 1), "*%x", i);
  805.     }
  806.  
  807.     if (!revs)
  808.     revs = "";
  809.     line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos)
  810.             + strlen (revs) + strlen (name) + 100);
  811.     sprintf (line, "%c%08lx|%s|%s|%s|%s|%s\n",
  812.          type, (long) time ((time_t *) NULL),
  813.          username, workdir, repos, revs, name);
  814.  
  815.     /* Lessen some race conditions on non-Posix-compliant hosts.  */
  816.     if (lseek (fd, (off_t) 0, SEEK_END) == -1)
  817.     error (1, errno, "cannot seek to end of history file: %s", fname);
  818.  
  819.     if (write (fd, line, strlen (line)) < 0)
  820.     error (1, errno, "cannot write to history file: %s", fname);
  821.     free (line);
  822.     if (close (fd) != 0)
  823.     error (1, errno, "cannot close history file: %s", fname);
  824. }
  825.  
  826. /*
  827.  * save_user() adds a user name to the user list to select.  Zero-length
  828.  *        username ("") matches any user.
  829.  */
  830. static void
  831. save_user (name)
  832.     char *name;
  833. {
  834.     if (user_count == user_max)
  835.     {
  836.     user_max += USER_INCREMENT;
  837.     user_list = (char **) xrealloc ((char *) user_list,
  838.                     (int) user_max * sizeof (char *));
  839.     }
  840.     user_list[user_count++] = xstrdup (name);
  841. }
  842.  
  843. /*
  844.  * save_file() adds file name and associated module to the file list to select.
  845.  *
  846.  * If "dir" is null, store a file name as is.
  847.  * If "name" is null, store a directory name with a '*' on the front.
  848.  * Else, store concatenated "dir/name".
  849.  *
  850.  * Later, in the "select" stage:
  851.  *    - if it starts with '*', it is prefix-matched against the repository.
  852.  *    - if it has a '/' in it, it is matched against the repository/file.
  853.  *    - else it is matched against the file name.
  854.  */
  855. static void
  856. save_file (dir, name, module)
  857.     char *dir;
  858.     char *name;
  859.     char *module;
  860. {
  861.     char *cp;
  862.     struct file_list_str *fl;
  863.  
  864.     if (file_count == file_max)
  865.     {
  866.     file_max += FILE_INCREMENT;
  867.     file_list = (struct file_list_str *) xrealloc ((char *) file_list,
  868.                            file_max * sizeof (*fl));
  869.     }
  870.     fl = &file_list[file_count++];
  871.     fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2);
  872.     fl->l_module = module;
  873.  
  874.     if (dir && *dir)
  875.     {
  876.     if (name && *name)
  877.     {
  878.         (void) strcpy (cp, dir);
  879.         (void) strcat (cp, "/");
  880.         (void) strcat (cp, name);
  881.     }
  882.     else
  883.     {
  884.         *cp++ = '*';
  885.         (void) strcpy (cp, dir);
  886.     }
  887.     }
  888.     else
  889.     {
  890.     if (name && *name)
  891.     {
  892.         (void) strcpy (cp, name);
  893.     }
  894.     else
  895.     {
  896.         error (0, 0, "save_file: null dir and file name");
  897.     }
  898.     }
  899. }
  900.  
  901. static void
  902. save_module (module)
  903.     char *module;
  904. {
  905.     if (mod_count == mod_max)
  906.     {
  907.     mod_max += MODULE_INCREMENT;
  908.     mod_list = (char **) xrealloc ((char *) mod_list,
  909.                        mod_max * sizeof (char *));
  910.     }
  911.     mod_list[mod_count++] = xstrdup (module);
  912. }
  913.  
  914. static void
  915. expand_modules ()
  916. {
  917. }
  918.  
  919. /* fill_hrec
  920.  *
  921.  * Take a ptr to 7-part history line, ending with a newline, for example:
  922.  *
  923.  *    M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
  924.  *
  925.  * Split it into 7 parts and drop the parts into a "struct hrec".
  926.  * Return a pointer to the character following the newline.
  927.  */
  928.  
  929. #define NEXT_BAR(here) do { while (isspace(*line)) line++; hr->here = line; while ((c = *line++) && c != '|') ; if (!c) return(rtn); *(line - 1) = '\0'; } while (0)
  930.  
  931. static char *
  932. fill_hrec (line, hr)
  933.     char *line;
  934.     struct hrec *hr;
  935. {
  936.     char *cp, *rtn;
  937.     int c;
  938.     int off;
  939.     static int idx = 0;
  940.     unsigned long date;
  941.  
  942.     memset ((char *) hr, 0, sizeof (*hr));
  943.     while (isspace (*line))
  944.     line++;
  945.     if (!(rtn = strchr (line, '\n')))
  946.     return ("");
  947.     *rtn++ = '\0';
  948.  
  949.     hr->type = line++;
  950.     (void) sscanf (line, "%lx", &date);
  951.     hr->date = date;
  952.     while (*line && strchr ("0123456789abcdefABCDEF", *line))
  953.     line++;
  954.     if (*line == '\0')
  955.     return (rtn);
  956.  
  957.     line++;
  958.     NEXT_BAR (user);
  959.     NEXT_BAR (dir);
  960.     if ((cp = strrchr (hr->dir, '*')) != NULL)
  961.     {
  962.     *cp++ = '\0';
  963.     (void) sscanf (cp, "%x", &off);
  964.     hr->end = line + off;
  965.     }
  966.     else
  967.     hr->end = line - 1;        /* A handy pointer to '\0' */
  968.     NEXT_BAR (repos);
  969.     NEXT_BAR (rev);
  970.     hr->idx = idx++;
  971.     if (strchr ("FOT", *(hr->type)))
  972.     hr->mod = line;
  973.  
  974.     NEXT_BAR (file);    /* This returns ptr to next line or final '\0' */
  975.     return (rtn);    /* If it falls through, go on to next record */
  976. }
  977.  
  978. /* read_hrecs's job is to read the history file and fill in all the "hrec"
  979.  * (history record) array elements with the ones we need to print.
  980.  *
  981.  * Logic:
  982.  * - Read the whole history file into a single buffer.
  983.  * - Walk through the buffer, parsing lines out of the buffer.
  984.  *   1. Split line into pointer and integer fields in the "next" hrec.
  985.  *   2. Apply tests to the hrec to see if it is wanted.
  986.  *   3. If it *is* wanted, bump the hrec pointer down by one.
  987.  */
  988. static void
  989. read_hrecs (fname)
  990.     char *fname;
  991. {
  992.     char *cp, *cp2;
  993.     int i, fd;
  994.     struct hrec *hr;
  995.     struct stat st_buf;
  996.  
  997.     if ((fd = open (fname, O_RDONLY | OPEN_BINARY)) < 0)
  998.     error (1, errno, "cannot open history file: %s", fname);
  999.  
  1000.     if (fstat (fd, &st_buf) < 0)
  1001.     error (1, errno, "can't stat history file");
  1002.  
  1003.     /* Exactly enough space for lines data */
  1004.     if (!(i = st_buf.st_size))
  1005.     error (1, 0, "history file is empty");
  1006.     cp = xmalloc (i + 2);
  1007.  
  1008.     if (read (fd, cp, i) != i)
  1009.     error (1, errno, "cannot read log file");
  1010.     (void) close (fd);
  1011.  
  1012.     if (*(cp + i - 1) != '\n')
  1013.     {
  1014.     *(cp + i) = '\n';        /* Make sure last line ends in '\n' */
  1015.     i++;
  1016.     }
  1017.     *(cp + i) = '\0';
  1018.     for (cp2 = cp; cp2 - cp < i; cp2++)
  1019.     {
  1020.     if (*cp2 != '\n' && !isprint (*cp2))
  1021.         *cp2 = ' ';
  1022.     }
  1023.  
  1024.     hrec_max = HREC_INCREMENT;
  1025.     hrec_head = (struct hrec *) xmalloc (hrec_max * sizeof (struct hrec));
  1026.  
  1027.     while (*cp)
  1028.     {
  1029.     if (hrec_count == hrec_max)
  1030.     {
  1031.         struct hrec *old_head = hrec_head;
  1032.  
  1033.         hrec_max += HREC_INCREMENT;
  1034.         hrec_head = (struct hrec *) xrealloc ((char *) hrec_head,
  1035.                        hrec_max * sizeof (struct hrec));
  1036.         if (hrec_head != old_head)
  1037.         {
  1038.         if (last_since_tag)
  1039.             last_since_tag = hrec_head + (last_since_tag - old_head);
  1040.         if (last_backto)
  1041.             last_backto = hrec_head + (last_backto - old_head);
  1042.         }
  1043.     }
  1044.  
  1045.     hr = hrec_head + hrec_count;
  1046.     cp = fill_hrec (cp, hr); /* cp == next line or '\0' at end of buffer */
  1047.  
  1048.     if (select_hrec (hr))
  1049.         hrec_count++;
  1050.     }
  1051.  
  1052.     /* Special selection problem: If "since_tag" is set, we have saved every
  1053.      * record from the 1st occurrence of "since_tag", when we want to save
  1054.      * records since the *last* occurrence of "since_tag".  So what we have
  1055.      * to do is bump hrec_head forward and reduce hrec_count accordingly.
  1056.      */
  1057.     if (last_since_tag)
  1058.     {
  1059.     hrec_count -= (last_since_tag - hrec_head);
  1060.     hrec_head = last_since_tag;
  1061.     }
  1062.  
  1063.     /* Much the same thing is necessary for the "backto" option. */
  1064.     if (last_backto)
  1065.     {
  1066.     hrec_count -= (last_backto - hrec_head);
  1067.     hrec_head = last_backto;
  1068.     }
  1069. }
  1070.  
  1071. /* Utility program for determining whether "find" is inside "string" */
  1072. static int
  1073. within (find, string)
  1074.     char *find, *string;
  1075. {
  1076.     int c, len;
  1077.  
  1078.     if (!find || !string)
  1079.     return (0);
  1080.  
  1081.     c = *find++;
  1082.     len = strlen (find);
  1083.  
  1084.     while (*string)
  1085.     {
  1086.     if (!(string = strchr (string, c)))
  1087.         return (0);
  1088.     string++;
  1089.     if (!strncmp (find, string, len))
  1090.         return (1);
  1091.     }
  1092.     return (0);
  1093. }
  1094.  
  1095. /* The purpose of "select_hrec" is to apply the selection criteria based on
  1096.  * the command arguments and defaults and return a flag indicating whether
  1097.  * this record should be remembered for printing.
  1098.  */
  1099. static int
  1100. select_hrec (hr)
  1101.     struct hrec *hr;
  1102. {
  1103.     char **cpp, *cp, *cp2;
  1104.     struct file_list_str *fl;
  1105.     int count;
  1106.  
  1107.     /* "Since" checking:  The argument parser guarantees that only one of the
  1108.      *              following four choices is set:
  1109.      *
  1110.      * 1. If "since_date" is set, it contains a Unix time_t specified on the
  1111.      *    command line. hr->date fields earlier than "since_date" are ignored.
  1112.      * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
  1113.      *    number (which is of limited use) or a symbolic TAG.  Each RCS file
  1114.      *    is examined and the date on the specified revision (or the revision
  1115.      *    corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
  1116.      *    compared against hr->date as in 1. above.
  1117.      * 3. If "since_tag" is set, matching tag records are saved.  The field
  1118.      *    "last_since_tag" is set to the last one of these.  Since we don't
  1119.      *    know where the last one will be, all records are saved from the
  1120.      *    first occurrence of the TAG.  Later, at the end of "select_hrec"
  1121.      *    records before the last occurrence of "since_tag" are skipped.
  1122.      * 4. If "backto" is set, all records with a module name or file name
  1123.      *    matching "backto" are saved.  In addition, all records with a
  1124.      *    repository field with a *prefix* matching "backto" are saved.
  1125.      *    The field "last_backto" is set to the last one of these.  As in
  1126.      *    3. above, "select_hrec" adjusts to include the last one later on.
  1127.      */
  1128.     if (since_date)
  1129.     {
  1130.     if (hr->date < since_date)
  1131.         return (0);
  1132.     }
  1133.     else if (*since_rev)
  1134.     {
  1135.     Vers_TS *vers;
  1136.     time_t t;
  1137.  
  1138.     vers = Version_TS (hr->repos, (char *) NULL, since_rev, (char *) NULL,
  1139.                hr->file, 1, 0, (List *) NULL, (RCSNode *) NULL);
  1140.     if (vers->vn_rcs)
  1141.     {
  1142.         if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
  1143.         != (time_t) 0)
  1144.         {
  1145.         if (hr->date < t)
  1146.         {
  1147.             freevers_ts (&vers);
  1148.             return (0);
  1149.         }
  1150.         }
  1151.     }
  1152.     freevers_ts (&vers);
  1153.     }
  1154.     else if (*since_tag)
  1155.     {
  1156.     if (*(hr->type) == 'T')
  1157.     {
  1158.         /*
  1159.          * A 'T'ag record, the "rev" field holds the tag to be set,
  1160.          * while the "repos" field holds "D"elete, "A"dd or a rev.
  1161.          */
  1162.         if (within (since_tag, hr->rev))
  1163.         {
  1164.         last_since_tag = hr;
  1165.         return (1);
  1166.         }
  1167.         else
  1168.         return (0);
  1169.     }
  1170.     if (!last_since_tag)
  1171.         return (0);
  1172.     }
  1173.     else if (*backto)
  1174.     {
  1175.     if (within (backto, hr->file) || within (backto, hr->mod) ||
  1176.         within (backto, hr->repos))
  1177.         last_backto = hr;
  1178.     else
  1179.         return (0);
  1180.     }
  1181.  
  1182.     /* User checking:
  1183.      *
  1184.      * Run down "user_list", match username ("" matches anything)
  1185.      * If "" is not there and actual username is not there, return failure.
  1186.      */
  1187.     if (user_list && hr->user)
  1188.     {
  1189.     for (cpp = user_list, count = user_count; count; cpp++, count--)
  1190.     {
  1191.         if (!**cpp)
  1192.         break;            /* null user == accept */
  1193.         if (!strcmp (hr->user, *cpp))    /* found listed user */
  1194.         break;
  1195.     }
  1196.     if (!count)
  1197.         return (0);            /* Not this user */
  1198.     }
  1199.  
  1200.     /* Record type checking:
  1201.      *
  1202.      * 1. If Record type is not in rec_types field, skip it.
  1203.      * 2. If mod_list is null, keep everything.  Otherwise keep only modules
  1204.      *    on mod_list.
  1205.      * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list".  If
  1206.      *    file_list is null, keep everything.  Otherwise, keep only files on
  1207.      *    file_list, matched appropriately.
  1208.      */
  1209.     if (!strchr (rec_types, *(hr->type)))
  1210.     return (0);
  1211.     if (!strchr ("TFO", *(hr->type)))    /* Don't bother with "file" if "TFO" */
  1212.     {
  1213.     if (file_list)            /* If file_list is null, accept all */
  1214.     {
  1215.         for (fl = file_list, count = file_count; count; fl++, count--)
  1216.         {
  1217.         /* 1. If file_list entry starts with '*', skip the '*' and
  1218.          *    compare it against the repository in the hrec.
  1219.          * 2. If file_list entry has a '/' in it, compare it against
  1220.          *    the concatenation of the repository and file from hrec.
  1221.          * 3. Else compare the file_list entry against the hrec file.
  1222.          */
  1223.         char cmpfile[PATH_MAX];
  1224.  
  1225.         if (*(cp = fl->l_file) == '*')
  1226.         {
  1227.             cp++;
  1228.             /* if argument to -p is a prefix of repository */
  1229.             if (!strncmp (cp, hr->repos, strlen (cp)))
  1230.             {
  1231.             hr->mod = fl->l_module;
  1232.             break;
  1233.             }
  1234.         }
  1235.         else
  1236.         {
  1237.             if (strchr (cp, '/'))
  1238.             {
  1239.             (void) sprintf (cp2 = cmpfile, "%s/%s",
  1240.                     hr->repos, hr->file);
  1241.             }
  1242.             else
  1243.             {
  1244.             cp2 = hr->file;
  1245.             }
  1246.  
  1247.             /* if requested file is found within {repos}/file fields */
  1248.             if (within (cp, cp2))
  1249.             {
  1250.             hr->mod = fl->l_module;
  1251.             break;
  1252.             }
  1253.         }
  1254.         }
  1255.         if (!count)
  1256.         return (0);        /* String specified and no match */
  1257.     }
  1258.     }
  1259.     if (mod_list)
  1260.     {
  1261.     for (cpp = mod_list, count = mod_count; count; cpp++, count--)
  1262.     {
  1263.         if (hr->mod && !strcmp (hr->mod, *cpp))    /* found module */
  1264.         break;
  1265.     }
  1266.     if (!count)
  1267.         return (0);    /* Module specified & this record is not one of them. */
  1268.     }
  1269.  
  1270.     return (1);        /* Select this record unless rejected above. */
  1271. }
  1272.  
  1273. /* The "sort_order" routine (when handed to qsort) has arranged for the
  1274.  * hrecs files to be in the right order for the report.
  1275.  *
  1276.  * Most of the "selections" are done in the select_hrec routine, but some
  1277.  * selections are more easily done after the qsort by "accept_hrec".
  1278.  */
  1279. static void
  1280. report_hrecs ()
  1281. {
  1282.     struct hrec *hr, *lr;
  1283.     struct tm *tm;
  1284.     int i, count, ty;
  1285.     char *cp;
  1286.     int user_len, file_len, rev_len, mod_len, repos_len;
  1287.  
  1288.     if (*since_tag && !last_since_tag)
  1289.     {
  1290.     (void) printf ("No tag found: %s\n", since_tag);
  1291.     return;
  1292.     }
  1293.     else if (*backto && !last_backto)
  1294.     {
  1295.     (void) printf ("No module, file or repository with: %s\n", backto);
  1296.     return;
  1297.     }
  1298.     else if (hrec_count < 1)
  1299.     {
  1300.     (void) printf ("No records selected.\n");
  1301.     return;
  1302.     }
  1303.  
  1304.     user_len = file_len = rev_len = mod_len = repos_len = 0;
  1305.  
  1306.     /* Run through lists and find maximum field widths */
  1307.     hr = lr = hrec_head;
  1308.     hr++;
  1309.     for (count = hrec_count; count--; lr = hr, hr++)
  1310.     {
  1311.     char repos[PATH_MAX];
  1312.  
  1313.     if (!count)
  1314.         hr = NULL;
  1315.     if (!accept_hrec (lr, hr))
  1316.         continue;
  1317.  
  1318.     ty = *(lr->type);
  1319.     (void) strcpy (repos, lr->repos);
  1320.     if ((cp = strrchr (repos, '/')) != NULL)
  1321.     {
  1322.         if (lr->mod && !strcmp (++cp, lr->mod))
  1323.         {
  1324.         (void) strcpy (cp, "*");
  1325.         }
  1326.     }
  1327.     if ((i = strlen (lr->user)) > user_len)
  1328.         user_len = i;
  1329.     if ((i = strlen (lr->file)) > file_len)
  1330.         file_len = i;
  1331.     if (ty != 'T' && (i = strlen (repos)) > repos_len)
  1332.         repos_len = i;
  1333.     if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
  1334.         rev_len = i;
  1335.     if (lr->mod && (i = strlen (lr->mod)) > mod_len)
  1336.         mod_len = i;
  1337.     }
  1338.  
  1339.     /* Walk through hrec array setting "lr" (Last Record) to each element.
  1340.      * "hr" points to the record following "lr" -- It is NULL in the last
  1341.      * pass.
  1342.      *
  1343.      * There are two sections in the loop below:
  1344.      * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
  1345.      *    decide whether the record should be printed.
  1346.      * 2. Based on the record type, format and print the data.
  1347.      */
  1348.     for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
  1349.     {
  1350.     char workdir[PATH_MAX], repos[PATH_MAX];
  1351.  
  1352.     if (!hrec_count)
  1353.         hr = NULL;
  1354.     if (!accept_hrec (lr, hr))
  1355.         continue;
  1356.  
  1357.     ty = *(lr->type);
  1358. #ifdef HAVE_RCS5
  1359.     if (!tz_local)
  1360.     {
  1361.         time_t t = lr->date + tz_seconds_east_of_GMT;
  1362.         tm = gmtime (&t);
  1363.     }
  1364.     else
  1365. #endif
  1366.     tm = localtime (&(lr->date));
  1367.     (void) printf ("%c %02d/%02d %02d:%02d %s %-*s", ty, tm->tm_mon + 1,
  1368.           tm->tm_mday, tm->tm_hour, tm->tm_min, tz_name,
  1369.           user_len, lr->user);
  1370.  
  1371.     (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
  1372.     if ((cp = strrchr (workdir, '/')) != NULL)
  1373.     {
  1374.         if (lr->mod && !strcmp (++cp, lr->mod))
  1375.         {
  1376.         (void) strcpy (cp, "*");
  1377.         }
  1378.     }
  1379.     (void) strcpy (repos, lr->repos);
  1380.     if ((cp = strrchr (repos, '/')) != NULL)
  1381.     {
  1382.         if (lr->mod && !strcmp (++cp, lr->mod))
  1383.         {
  1384.         (void) strcpy (cp, "*");
  1385.         }
  1386.     }
  1387.  
  1388.     switch (ty)
  1389.     {
  1390.         case 'T':
  1391.         /* 'T'ag records: repository is a "tag type", rev is the tag */
  1392.         (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
  1393.                    repos);
  1394.         if (working)
  1395.             (void) printf (" {%s}", workdir);
  1396.         break;
  1397.         case 'F':
  1398.         case 'O':
  1399.         if (lr->rev && *(lr->rev))
  1400.             (void) printf (" [%s]", lr->rev);
  1401.         (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
  1402.                    mod_len + 1 - strlen (lr->mod), "=", workdir);
  1403.         break;
  1404.         case 'W':
  1405.         case 'U':
  1406.         case 'C':
  1407.         case 'G':
  1408.         case 'M':
  1409.         case 'A':
  1410.         case 'R':
  1411.         (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
  1412.                    file_len, lr->file, repos_len, repos,
  1413.                    lr->mod ? lr->mod : "", workdir);
  1414.         break;
  1415.         default:
  1416.         (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
  1417.         break;
  1418.     }
  1419.     (void) putchar ('\n');
  1420.     }
  1421. }
  1422.  
  1423. static int
  1424. accept_hrec (lr, hr)
  1425.     struct hrec *hr, *lr;
  1426. {
  1427.     int ty;
  1428.  
  1429.     ty = *(lr->type);
  1430.  
  1431.     if (last_since_tag && ty == 'T')
  1432.     return (1);
  1433.  
  1434.     if (v_checkout)
  1435.     {
  1436.     if (ty != 'O')
  1437.         return (0);            /* Only interested in 'O' records */
  1438.  
  1439.     /* We want to identify all the states that cause the next record
  1440.      * ("hr") to be different from the current one ("lr") and only
  1441.      * print a line at the allowed boundaries.
  1442.      */
  1443.  
  1444.     if (!hr ||            /* The last record */
  1445.         strcmp (hr->user, lr->user) ||    /* User has changed */
  1446.         strcmp (hr->mod, lr->mod) ||/* Module has changed */
  1447.         (working &&            /* If must match "workdir" */
  1448.          (strcmp (hr->dir, lr->dir) ||    /*    and the 1st parts or */
  1449.           strcmp (hr->end, lr->end))))    /*    the 2nd parts differ */
  1450.  
  1451.         return (1);
  1452.     }
  1453.     else if (modified)
  1454.     {
  1455.     if (!last_entry ||        /* Don't want only last rec */
  1456.         !hr ||            /* Last entry is a "last entry" */
  1457.         strcmp (hr->repos, lr->repos) ||    /* Repository has changed */
  1458.         strcmp (hr->file, lr->file))/* File has changed */
  1459.         return (1);
  1460.  
  1461.     if (working)
  1462.     {                /* If must match "workdir" */
  1463.         if (strcmp (hr->dir, lr->dir) ||    /*    and the 1st parts or */
  1464.         strcmp (hr->end, lr->end))    /*    the 2nd parts differ */
  1465.         return (1);
  1466.     }
  1467.     }
  1468.     else if (module_report)
  1469.     {
  1470.     if (!last_entry ||        /* Don't want only last rec */
  1471.         !hr ||            /* Last entry is a "last entry" */
  1472.         strcmp (hr->mod, lr->mod) ||/* Module has changed */
  1473.         strcmp (hr->repos, lr->repos) ||    /* Repository has changed */
  1474.         strcmp (hr->file, lr->file))/* File has changed */
  1475.         return (1);
  1476.     }
  1477.     else
  1478.     {
  1479.     /* "extract" and "tag_report" always print selected records. */
  1480.     return (1);
  1481.     }
  1482.  
  1483.     return (0);
  1484. }
  1485.